using System;
using System.Text;
using System.Data;
using System.Collections;
using System.Collections.Generic;
using System.Net;
using System.Web.Script.Serialization;
using Framework;
using SchemaMonitor.json;

namespace SchemaMonitor
{
    //Table-Mapping Class (Customisable half)
    public partial class CType
    {
        #region Constants
        private readonly static Tuple<string, string>[] _fbManualTypes = 
        {
            new Tuple<string, string>("User", "me"),
            new Tuple<string, string>("Post", "me/posts"),
            new Tuple<string, string>("Tagged", "me/tagged"),
            new Tuple<string, string>("Like", "me/likes"),
            new Tuple<string, string>("Photo", "me/photos"),
            new Tuple<string, string>("Album", "me/albums"),
            new Tuple<string, string>("Event", "me/events"),
            new Tuple<string, string>("Video", "me/videos"),
        };
        #endregion

        #region Web
        //Download As
        public CPage DownloadPage(CUser u, WebClient wc, JavaScriptSerializer jss)
        {
            return CEngine.DownloadPage(UrlGetPage(u), wc, jss);
        }
        public CDict DownloadItem(CUser u, WebClient wc, JavaScriptSerializer jss)
        {
            return CEngine.DownloadDict(UrlGetPage(u), wc, jss);
        }

        //Url
        public string UrlGetPage(CUser u)
        {
            return Schema.UrlGetByType(this, u);
        }
        public string UrlGetPrev(CUser u, string before)
        {
            return Schema.UrlGetByType_Prev(this, u, before);
        }
        public string UrlGetNext(CUser u, string after)
        {
            return Schema.UrlGetByType_Next(this, u, after);
        }
        #endregion

        #region Constructors (Public)
        //Default Connection String
        public CType() : base() {}

        //Alternative Connection String
        public CType(CDataSrc dataSrc) : base(dataSrc) {}
        
        //Hidden  (UI code should use cache instead)
        protected internal CType(int typeId) : base(typeId) {}
        protected internal CType(CDataSrc dataSrc, int typeId) : base(dataSrc, typeId) {}
        protected internal CType(CDataSrc dataSrc, int typeId, IDbTransaction txOrNull) : base(dataSrc, typeId, txOrNull) { }

        //static
        static CType()
        {
            if (CSchema.FB.SchemaId == CSchema.OnlySchemaId)
                foreach (var i in _fbManualTypes)
                    CType.Cache.GetOrCreate_Manual(i.Item1, i.Item2);
        }
        #endregion

        private json.CMetadata _metadata;

        #region Default Values
        protected override void InitValues_Custom()
        {
            _typeSchemaId = CSchema.DefaultId;
            _typeCreated = DateTime.Now;
            _typeUpdated = DateTime.Now;
        }
        #endregion
        
        #region Default Connection String
        protected override CDataSrc DefaultDataSrc()  { return CDataSrc.Default;  }
        #endregion

        #region Properties - Relationships
        //Relationships - Foriegn Keys (e.g parent)
        public CSchema    Schema    { get { return CSchema.Cache.GetById(this.TypeSchemaId); } }
        public CDiscovery Discovery { get { return CDiscovery.Cache.GetById(this.TypeDiscoveryId); } }

        //Relationships - Collections (e.g. children)
        public   CNodeList Nodes()                 {   return new CNode(DataSrc).SelectByTypeId(     this.TypeId    ); } 
        public   CNodeList Nodes(CPagingInfo pi)   {   return new CNode(DataSrc).SelectByTypeId(pi,  this.TypeId    ); } 
        internal CNodeList Nodes(IDbTransaction tx){   return new CNode(DataSrc).SelectByTypeId(     this.TypeId, tx); } 
        public   int             NodesCount()            {   return new CNode(DataSrc).SelectCountByTypeId(this.TypeId    ); }


        public    CSignatureList Signatures                     {  get { return CSignature.Cache.GetByTypeId(          this.TypeId); }  }
        protected CSignatureList Signatures_(IDbTransaction tx) {        return new CSignature(DataSrc).SelectByTypeId(this.TypeId, tx); } //Only used for cascade deletes

        public    CTagList Tags                     {  get { return CTag.Cache.GetByForTypeId(          this.TypeId); }  }
        public    CTagList UsedByTags               {  get { return CTag.Cache.GetByIsTypeId(          this.TypeId); }  }
        protected CTagList Tags_(IDbTransaction tx) {        return new CTag(DataSrc).SelectByForTypeId(this.TypeId, tx); } //Only used for cascade deletes
        #endregion

        #region Properties - Customisation
        public bool HasMeta { get { return !Guid.Empty.Equals(TypeMetaHash); } }
        public json.CMetadata Metadata
        {
            get
            {
                if (! HasMeta)
                    return null;
                if (null == _metadata)
                    _metadata = Deser(MetaBin);
                return _metadata;
            }
            set
            {
                _metadata = value;

                //Binary-ser
                MetaBin = Ser(value);
				if (null == value)
					return;

                //Set name/hasId
                if (value.HasType)
                    _typeName = value.Type;
                _typeHasId = value.Has("id");
            }
        }
        private byte[] MetaBin
        {
            get { return Unz(TypeMetaBinGz);         }
            set
            {
                TypeMetaBinGz = Zip(value);
                TypeMetaHash = Hash(value);
            }
        }

        private static CMetadata Deser(byte[] b) { return null == b ? null : CProto.Deserialise<CMetadata>(b); }
        private static byte[] Ser(CMetadata m) { return null == m ? null : CProto.Serialise(m); }
        public static byte[] Zip(byte[] b) { return null == b ? null : CBinary.Zip(b); }
        public static byte[] Unz(byte[] b) { return null == b ? null : CBinary.Unzip(b); }
        public static Guid Hash(byte[] b) { return null == b ? Guid.Empty: CBinary.Sha128_(b); }
        public static Guid Hash(CMetadata m) { return Hash(Ser(m)); }

        public EDiscovery DiscoveryId {  get { return (EDiscovery)TypeDiscoveryId; } set { TypeDiscoveryId = (byte)value; } }
        public bool IsSystem    { get { return DiscoveryId == EDiscovery.System; } }
        public bool IsAuto      { get { return DiscoveryId == EDiscovery.Auto;   } }
        public bool IsManual    { get { return DiscoveryId == EDiscovery.Manual; } }
        public bool IsComposite { get { return IsManual || IsAuto; } }

        public bool HasId       { get { return DiscoveryId == EDiscovery.Manual || TypeHasId; } }
        public bool IsReference { get { return IsAuto && HasId || IsManual; } }
        public bool IsDataset   { get { return Signatures.IsDataset; } }
        //public bool Is(Type t) { return Signatures.Is(t); }

        public string NameOrId {  get { return string.IsNullOrEmpty(TypeName) ? TypeId.ToString() : TypeName; } }
        public string NameShort { get { return TypeName.Replace("System.", string.Empty); } }



        //Create a tag, in the context of this type
        public CTag GetOrCreateTag(CMetaField meta, object value, CUser c)
        {
            //Get or create
            var tag = this.Tags.GetByName(meta.Name);
            if (null == tag)
            {
                tag = new CTag();
                tag.TagForTypeId = this.TypeId;
            }

            //Check tag-name
            tag.TagName = meta.Name;
            tag.TagDescription = meta.Description;

            //List<Type> => IsList + Type
            var t = meta.Type;
            if (string.IsNullOrEmpty(t))
                t = "object";
			if (t.StartsWith(CSignature.L1))
			{
				tag.TagIsList = true;
				t = t.Substring(5);
				t = t.Substring(0, t.Length - 1);

				var arr = (object[])value;
				if (arr.Length > 0)
					value = arr[0];
			}
			else if (t == "list")
			{
				tag.TagIsList = true;
				var arr = (object[])value;
				if (arr.Length > 0)
					value = arr[0];
			}



			var sigName = new CSignature(t);

            //check field type
            CType tt = null;
            var match = CSignature.Cache.GetByHash(sigName);
            if (match.Count == 1)
                tt = match[0].Type;
            else
            {
                object val = null;
                if (value is Dictionary<string, object>)
                {
                    var d = (Dictionary<string, object>)value;
                    if (d.TryGetValue(meta.Name, out val))
                        value = val; //parent/child confusion?? remove
                }
                else if (tag.TagIsList)
                {
                    var arr = (object[])value;
                    var b = true;
                    if (arr.Length > 0)
                    {
                        var obj = arr[0];
                        if (obj is Dictionary<string, object>)
                        {
                            tt = Cache.GetOrCreate_Auto(obj, tag.Path, c, out b);
                            if (tt.TypeName.Length == 0)
                                tt.TypeName = t;
                        }
                        else
                            tt = Cache.GetOrCreate_System(obj.GetType());
                    }
                    else
                        tt = Cache.GetOrCreate_System(t);
                }
                else
                    tt = Cache.GetOrCreate_System(value.GetType());
            }
            
            if (null == tt)
            {
                bool b = false;
                tt = Cache.GetOrCreate_Auto(value, tag.Path, c, out b);
				CTypeList.AddSignatures(tt, new CSignature(tag.Path));
                if (b)
                    tag.TagIsList = true;
            }
            CTypeList.AddSignatures(tt, sigName, new CSignature(t));

			//Map type to tag
			if (tag.TagId <= 0 || tag.TagIsTypeId != tt.TypeId)
			{
				tag.TagIsTypeId = tt.TypeId;
				tag.Save();
			}
			return tag;
        }
        public CTag GetOrCreateTag(string key, object value, CUser c)
        {
			if (HasMeta)
				if (this.Metadata.Has(key))
					return GetOrCreateTag(this.Metadata[key], value, c);
				else
				{
					if (key == "tags")
					{
						Console.WriteLine("Skipped: " + key);
						return null;
					}
				}
	
            //Match tag by name
            var tag = this.Tags.GetByName(key);
            if (null != tag)
                return tag;

            //Create new
            tag = new CTag();
            tag.TagForTypeId = this.TypeId;     //Parent
            tag.TagName = key;

			if (value is IList)
				if (! value.GetType().ToString().Contains("[]"))
					throw new Exception("Pass array, not list: " + key);

            //Determine field type
            var s = CSignature.GetSigAuto(tag.Path, value);
            var s2 = CSignature.GetSigAuto(string.Empty, value);
            var isList = s.IsList;

            if (s2.IsDataSet)
            {
                var p = new CPage(value);
                value = p.Data;
                isList = true;
                s = CSignature.GetSigAuto(tag.Path, value);
                s2 = CSignature.GetSigAuto(tag.Path, value);
            }


            CType t = null;
            if (s.IsSystem)
            {
                value = CSignature.JsonToNative(value);
                t = CType.Cache.GetOrCreate_System(value.GetType());
            }
            else if (s.IsDict && ! s.IsList)
            {
                var d = (Dictionary<string, object>)value;
                t = CType.Cache.GetOrCreate(tag.Path, d, c);
            }
            else
                t = CType.Cache.GetOrCreate_Auto(value, tag.Path, c, out isList);

            //Create new tag
            tag.TagIsList = isList;      //Format
            tag.TagIsTypeId = t.TypeId; //Format
            tag.Save();
            return tag;
        }
        #endregion

        #region Save/Delete Overrides
        //Can Override base.Save/Delete (e.g. Cascade deletes, or insert related records)
        public override void Delete(IDbTransaction txOrNull)
        {
            //Use a transaction if none supplied 
            if (txOrNull == null)
            {
                BulkDelete(this);
                return;
            }

            //Cascade-Delete (all child collections) 
            this.Tags_(txOrNull).DeleteAll(txOrNull);
            this.Signatures_(txOrNull).DeleteAll(txOrNull);


            //Normal Delete 
            base.Delete(txOrNull);
        }

        #endregion

        #region Custom Database Queries
        //(Not normally required for cached classes, use list class for searching etc)
        //For Stored Procs can use: MakeList (matching schema), or DataSrc.ExecuteDataset (reports etc)
        //For Dynamic sql, can use: SelectSum, SelectDistinct, SelectCount, SelectWhere (inherited methods)

        internal CTypeList SelectInScope()
        {
            return SelectWhere(InScope());
        }
        internal static CCriteriaList InScope()
        {
            return CSchema.InScope( "TypeSchemaId");
        }
        #endregion


        #region Caching Details
        //Cache Key
        internal static string CACHE_KEY = typeof(CType).ToString();    //TABLE_NAME

        //Cache data
        private static CTypeList LoadCache()  {   return new CType().SelectInScope();   }

        //Cache Timeout
        private static void SetCache(CTypeList value)
        {
            if (null != value)  
                value.Sort(); 
            CCache.Set(CACHE_KEY, value);    //Optional parameter can override timeout (otherwise uses config-settings, which default to 3hrs)

        }
        //Helper Method
        private CType CacheGetById(CTypeList list)  { return list.GetById(this.TypeId);    }
        #endregion

        #region Cloning
        public CType Clone(CDataSrc target, IDbTransaction txOrNull) //, int parentId)
        {
            //Shallow copy: Copies the immediate record, excluding autogenerated Pks
            CType copy = new CType(this, target);

            //Deep Copy - Child Entities: Cloned children must reference their cloned parent
            //copy.SampleParentId = parentId;

            copy.Save(txOrNull);

            //Deep Copy - Parent Entities: Cloned parents also clone their child collections
            //this.Children.Clone(target, txOrNull, copy.TypeId);

            return copy;
        }
        #endregion

        #region ToXml
        protected override void ToXml_Custom(System.Xml.XmlWriter w)
        {
            //Store(w, "Example", this.Example)
        }
        #endregion
    }
}